#include "qnetworkhttpclient.h"

#include <QThread>
#include <QNetworkRequest>
#include <QFile>
#include <QHttpMultiPart>
#include <QNetworkReply>
#include <QUrlQuery>
#include <QJsonValue>
#include <QJsonObject>
#include <QFileInfo>
#include <QDir>
#include <QDebug>
#include <QCoreApplication>

#include "httplib.h"
#include <Windows.h>
#include <iostream>
#include <chrono>
#include <memory>

class QNetworkHttpClientPrivate {
	friend class QNetworkHttpClient;

	QNetworkHttpClientPrivate(const QString& server, int port);
	~QNetworkHttpClientPrivate();

	void reconnect();

	std::shared_ptr<httplib::Client> m_HttpClient;

	QString m_server;
	int  m_port;
};

QNetworkHttpClientPrivate::QNetworkHttpClientPrivate(const QString& server, int port):m_server(server), m_port(port)
{
	m_HttpClient = std::make_shared<httplib::Client>(server.toUtf8().constData(), port);

}

QNetworkHttpClientPrivate::~QNetworkHttpClientPrivate() {
	
}

void QNetworkHttpClientPrivate::reconnect()
{
	m_HttpClient = std::make_shared<httplib::Client>(m_server.toUtf8().constData(), m_port);
}

QNetworkHttpClient::QNetworkHttpClient(const QString& server, int port,QObject *parent)
	: QObject(parent), d(new QNetworkHttpClientPrivate(server,port))
{
	d->m_HttpClient->set_keep_alive(false);
}

QNetworkHttpClient::~QNetworkHttpClient()
{
	delete d;
}

QSharedPointer<ResponseContent> QNetworkHttpClient::getJsonSync(const QString& subPath, int timeout, int tryTimes)
{
	QSharedPointer<ResponseContent>  result = QSharedPointer<ResponseContent>::create();

	result->StatusCode = -1;

	while (tryTimes > 0)
	{
		d->m_HttpClient->set_connection_timeout(timeout / 1000);
		d->m_HttpClient->set_read_timeout(timeout / 1000);
		d->m_HttpClient->set_write_timeout(timeout / 1000);

		auto start = std::chrono::high_resolution_clock::now();
		auto res = d->m_HttpClient->Get(subPath.toUtf8().constData());

		auto end = std::chrono::high_resolution_clock::now();
		auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

		result->RTT = elapsed.count();

		if (res)
		{
			result->StatusCode = res->status;
			result->BodyContent = QString::fromUtf8(res->body.c_str());
			break;
		}
		else
		{
			result->StatusCode = -1;
			auto err = res.error();
			std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
			result->BodyContent = QString::fromUtf8(httplib::to_string(err).c_str());

			if (err == httplib::Error::ConnectionTimeout ||
				err == httplib::Error::Connection ||
				err == httplib::Error::Read ||
				err == httplib::Error::Write ||
				err == httplib::Error::SSLConnection ||
				err == httplib::Error::ProxyConnection)
			{
				tryTimes--;

				d->reconnect();
				continue;
			}
		}


	}

	return result;
}

QSharedPointer<ResponseContent> QNetworkHttpClient::postJsonSync(const QString& subPath, const QString& sendJsonContent, int timeout, int tryTimes)
{
	QSharedPointer<ResponseContent>  result = QSharedPointer<ResponseContent>::create();

	while (tryTimes > 0)
	{
		d->m_HttpClient->set_connection_timeout(timeout / 1000);
		d->m_HttpClient->set_read_timeout(timeout / 1000);
		d->m_HttpClient->set_write_timeout(timeout / 1000);

		auto start = std::chrono::high_resolution_clock::now();
		auto res = d->m_HttpClient->Post(subPath.toUtf8().constData(), sendJsonContent.toUtf8().constData(), "application/json");

		auto end = std::chrono::high_resolution_clock::now();
		auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

		result->RTT = elapsed.count();
		if (res)
		{
			result->StatusCode = res->status;
			result->BodyContent = QString::fromUtf8(res->body.c_str());
			break;
		}
		else
		{
			result->StatusCode = -1;
			auto err = res.error();
			std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
			result->BodyContent = QString::fromUtf8(httplib::to_string(err).c_str());

			if (err == httplib::Error::ConnectionTimeout ||
				err == httplib::Error::Connection ||
				err == httplib::Error::Read ||
				err == httplib::Error::Write ||
				err == httplib::Error::SSLConnection ||
				err == httplib::Error::ProxyConnection)
			{
				tryTimes--;

				d->reconnect();
				continue;
			}
		}
	}

	return result;
}

QFuture<QSharedPointer<ResponseContent>> QNetworkHttpClient::getJsonASync(const QString& subPath, int timeout, int tryTimes)
{
	QFuture<QSharedPointer<ResponseContent>> future = QtConcurrent::run([this, subPath,timeout,tryTimes]() {
		return getJsonSync(subPath, timeout, tryTimes);
	});

	return future;
}

QFuture<QSharedPointer<ResponseContent>> QNetworkHttpClient::postJsonASync(const QString& subPath, const QString& sendJsonContent, int timeout, int tryTimes)
{
	QFuture<QSharedPointer<ResponseContent>> future = QtConcurrent::run([this, &subPath, &sendJsonContent,timeout, tryTimes]() {
		return postJsonSync(subPath, sendJsonContent, timeout, tryTimes);
	});

	return future;
}

QSharedPointer<ResponseContent> QNetworkHttpClient::getJsonASyncNonblocking(const QString& subPath, int timeout, int tryTimes)
{
	auto future = getJsonASync(subPath, timeout, tryTimes);
	QEventLoop loop;
	QFutureWatcher<QSharedPointer<ResponseContent>> watcher;
	QObject::connect(&watcher, &QFutureWatcher<QSharedPointer<ResponseContent>>::finished, &loop, &QEventLoop::quit);
	watcher.setFuture(future);
	loop.exec();
	return future.result();
}

QSharedPointer<ResponseContent> QNetworkHttpClient::postJsonASyncNonblocking(const QString& subPath, const QString& sendJsonContent, int timeout, int tryTimes)
{
	auto future = postJsonASync(subPath, sendJsonContent, timeout, tryTimes);
	QEventLoop loop;
	QFutureWatcher<QSharedPointer<ResponseContent>> watcher;
	QObject::connect(&watcher, &QFutureWatcher<QSharedPointer<ResponseContent>>::finished, &loop, &QEventLoop::quit);
	watcher.setFuture(future);
	loop.exec();
	return future.result();
}

QSharedPointer<ResponseContent> QNetworkHttpClient::postFileFromSync(const QString& subPath, const QVector<QString>& fileList, int timeout, int tryTimes)
{
	QMap<QString, QString> multiFiles;
	QMap<QString, QString> multiContent;

	QSharedPointer<ResponseContent>  result = QSharedPointer<ResponseContent>::create();

	httplib::MultipartFormDataItems items;

	for (QMap<QString, QString>::const_iterator it = multiContent.constBegin(); it != multiContent.constEnd(); ++it)
	{
		items.push_back(
			{ it.key().toUtf8().constData(), it.value().toUtf8().constData(), "", ""});
	}

	httplib::MultipartFormDataProviderItems provider_items;
	for (QMap<QString, QString>::const_iterator it = multiFiles.constBegin(); it != multiFiles.constEnd(); ++it)
	{
		QString content_type;
		QFileInfo fileInfo(it.value());

		if (!fileInfo.exists()) continue;

		QString ext = fileInfo.suffix();
		if (ext.toLower().contains("bmp"))
		{
			content_type = "image/bmp";
		}
		else if (ext.toLower().contains("json"))
		{
			content_type = "application/json";
		}
		else
		{
			continue;
		}

		std::wstring file_path = it.value().toStdWString();
		std::ifstream ifs(file_path, std::ifstream::binary);
		if (!ifs) continue;

		ifs.seekg(0, ifs.end);
		auto size = ifs.tellg();
		ifs.seekg(0, ifs.beg);

		httplib::MultipartFormDataProvider provider;
		provider.name = it.key().toUtf8().constData();
		provider.filename = QFileInfo(it.value()).fileName().toUtf8().constData();

		const std::streamsize chunk_size = 4096;
		std::vector<unsigned char> buffer(chunk_size);

		provider.provider = [&](size_t offset, httplib::DataSink& sink) {

			if (offset == size)
			{
				sink.done();
				ifs.close();
			}
			else
			{
				std::streamsize to_read = (size - ifs.tellg()) > chunk_size ? chunk_size : (size - ifs.tellg());
				buffer.resize(to_read);

				ifs.read(reinterpret_cast<char*>(buffer.data()), to_read);
				sink.os.write(reinterpret_cast<char*>(buffer.data()), buffer.size());
			}

			return true;
		};
		provider.content_type = "multipart/form-data";
		provider_items.push_back(provider);
	}

	d->m_HttpClient->set_connection_timeout(timeout / 1000);
	d->m_HttpClient->set_read_timeout(timeout / 1000);
	d->m_HttpClient->set_write_timeout(timeout / 1000);

	while (tryTimes > 0)
	{
		auto start = std::chrono::high_resolution_clock::now();
		httplib::Result res = d->m_HttpClient->Post(subPath.toUtf8().constData(), {}, items, provider_items);
		auto end = std::chrono::high_resolution_clock::now();
		auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

		result->RTT = elapsed.count();
		if (res)
		{
			result->StatusCode = res->status;
			result->BodyContent = QString::fromUtf8(res->body.c_str());
			break;
		}
		else
		{
			result->StatusCode = -1;
			auto err = res.error();
			std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
			result->BodyContent = QString::fromUtf8(httplib::to_string(err).c_str());

			if (err == httplib::Error::ConnectionTimeout ||
				err == httplib::Error::Connection ||
				err == httplib::Error::Read ||
				err == httplib::Error::Write ||
				err == httplib::Error::SSLConnection ||
				err == httplib::Error::ProxyConnection)
			{
				tryTimes--;
				d->reconnect();
				continue;
			}
		}
	}

	return result;
}
